#include "make_route.h"
#include "contest_util.h"

namespace ieda_contest {

typedef gridmap::Map<astar::Node<3>> Map;

MakeRoute::MakeRoute(ContestDataManager* data_manager){
  _data_manager = data_manager;
  createMap();
}

void MakeRoute::createMap()
{
  std::map<int, int>& layer_gcell_supply_map = _data_manager->get_database()->get_layer_gcell_supply_map();

  double gcellWidth = _data_manager->get_database()->get_single_gcell_x_span();
  double gcellHeight = _data_manager->get_database()->get_single_gcell_y_span();
  int gcell_map_size_x = static_cast<int>(_data_manager->get_idb_layout()->get_die()->get_width() / gcellWidth + 1);
  int gcell_map_size_y = static_cast<int>(_data_manager->get_idb_layout()->get_die()->get_height() / gcellHeight + 1);
  int gcell_map_size_z = _data_manager->get_database()->get_layer_name_to_idx_map().size();

  _grid_map = Map(_data_manager, gcell_map_size_x, gcell_map_size_y, gcell_map_size_z, static_cast<int>(gcellWidth),
                  static_cast<int>(gcellHeight));
  _pathfinder.set_map(_grid_map);

  for (int i = 0; i < gcell_map_size_z; ++i) {
    _grid_map.fillLayerSupplyResourceCnt(layer_gcell_supply_map[i] / 2, i);
  }
}

void MakeRoute::doRoute(ContestNet* contest_net) {
  // key_coord and routing_segment_list
  std::vector<ContestCoord> key_coord_list = makeKeyCoordList(contest_net);
  std::vector<ContestSegment>& routing_segment_list = contest_net->get_routing_segment_list();
  routing_segment_list.clear();

  // route by flute
  for (ContestSegment& topo : getTopoListByFlute(key_coord_list)) {
    for (ContestSegment& routing_segment : getRoutingSegmentList(topo, contest_net)) {
      routing_segment_list.push_back(routing_segment);
    }
  }
  updateGuide(contest_net);
}

std::vector<ContestCoord> MakeRoute::makeKeyCoordList(ContestNet* contest_net)
{
  std::vector<ContestCoord> key_coord_list;
  for (ContestPin& pin : contest_net->get_pin_list()) {
    key_coord_list.push_back(pin.get_grid_coord());
  }
  std::sort(key_coord_list.begin(), key_coord_list.end(), CmpContestCoord());
  key_coord_list.erase(std::unique(key_coord_list.begin(), key_coord_list.end()), key_coord_list.end());
  return key_coord_list;
}

std::vector<ContestSegment> MakeRoute::getTopoListByFlute(std::vector<ContestCoord>& coord_list)
{
  size_t coord_num = coord_list.size();
  if (coord_num == 1) {
    return {};
  }
  int max_layer_idx = INT_MIN;
  int min_layer_idx = INT_MAX;
  for (ContestCoord& coord : coord_list) {
    std::max(max_layer_idx, coord.get_layer_idx());
    std::min(min_layer_idx, coord.get_layer_idx());
  }
  int layer_idx = (max_layer_idx + min_layer_idx) / 2;

  Flute::DTYPE* x_list = (Flute::DTYPE*) malloc(sizeof(Flute::DTYPE) * (coord_num));
  Flute::DTYPE* y_list = (Flute::DTYPE*) malloc(sizeof(Flute::DTYPE) * (coord_num));
  for (size_t i = 0; i < coord_num; i++) {
    x_list[i] = coord_list[i].get_x();
    y_list[i] = coord_list[i].get_y();
  }
  Flute::Tree flute_tree = Flute::flute(coord_num, x_list, y_list, FLUTE_ACCURACY);
  // Flute::printtree(flute_tree);
  free(x_list);
  free(y_list);

  std::vector<ContestSegment> topo_list;
  for (int i = 0; i < 2 * flute_tree.deg - 2; i++) {
    int n_id = flute_tree.branch[i].n;
    ContestCoord first_coord(flute_tree.branch[i].x, flute_tree.branch[i].y, layer_idx);
    ContestCoord second_coord(flute_tree.branch[n_id].x, flute_tree.branch[n_id].y, layer_idx);
    topo_list.emplace_back(first_coord, second_coord);
  }
  Flute::free_tree(flute_tree);
  return topo_list;
}

std::vector<ContestSegment> MakeRoute::getRoutingSegmentList(ContestSegment& topo, ContestNet* contest_net)
{
  std::vector<ContestSegment> routing_segment_list;

  _pathfinder.set_starting_position({topo.get_first().get_x(), topo.get_first().get_y(), topo.get_first().get_layer_idx()});
  _pathfinder.set_ending_position({topo.get_second().get_x(), topo.get_second().get_y(), topo.get_second().get_layer_idx()});

  auto path = _pathfinder.findPath();

  if (path.empty()) {
    std::cout << "Debug: " << contest_net->get_net_name() << " cant't find path\n";
  }

  if (!path.empty()) {
    ContestCoord segment_starting_coord(path[0]->get_position().get<0>(), path[0]->get_position().get<1>(),
                                        path[0]->get_position().get<2>());

    for (int i = 1; i < (int) path.size(); ++i) {
      ContestCoord starting_coord(path[i - 1]->get_position().get<0>(), path[i - 1]->get_position().get<1>(),
                                  path[i - 1]->get_position().get<2>());
      ContestCoord ending_coord(path[i]->get_position().get<0>(), path[i]->get_position().get<1>(), path[i]->get_position().get<2>());
      if (i + 1 != (int) path.size()) {
        ContestCoord next_coord(path[i + 1]->get_position().get<0>(), path[i + 1]->get_position().get<1>(),
                                path[i + 1]->get_position().get<2>());
        if (ending_coord - starting_coord != next_coord - ending_coord) {
          ContestSegment new_segment(segment_starting_coord, ending_coord);
          routing_segment_list.emplace_back(new_segment);

          segment_starting_coord = ending_coord;
        }
      } else {
        ContestSegment new_segment(segment_starting_coord, ending_coord);
        routing_segment_list.emplace_back(new_segment);
      }

      // decrease remaining resourse when through a gcell
      if (starting_coord.get_layer_idx() == ending_coord.get_layer_idx()) {
        // std::cout << "Debug: Layer " << ending_coord.get_layer_idx() << std::endl;
        Map::point starting_gcell = Map::point(starting_coord.get_x(), starting_coord.get_y(), starting_coord.get_layer_idx());
        Map::point ending_gcell = Map::point(ending_coord.get_x(), ending_coord.get_y(), ending_coord.get_layer_idx());
        // _grid_map[ending_gcell].addNetId(contest_net->get_id());
        // _grid_map[starting_gcell].addNetId(contest_net->get_id());
        _grid_map[ending_gcell].addNetId(reinterpret_cast<long>(&contest_net));
        _grid_map[starting_gcell].addNetId(reinterpret_cast<long>(&contest_net));
      }
    }
  }

  return routing_segment_list;
}

void MakeRoute::updateGuide(ContestNet* contest_net) {
  contest_net->get_guide_list().clear();
    for (ContestSegment& routing_segment : contest_net->get_routing_segment_list()) {
      for (ContestGuide& guide : getGuide(routing_segment)) {
        contest_net->get_guide_list().push_back(guide);
      }
    }
}

std::vector<ContestGuide> MakeRoute::getGuide(ContestSegment& routing_segment)
{
  std::map<int, std::string>& layer_idx_to_name_map = _data_manager->get_database()->get_layer_idx_to_name_map();
  int single_gcell_x_span = _data_manager->get_database()->get_single_gcell_x_span();
  int single_gcell_y_span = _data_manager->get_database()->get_single_gcell_y_span();

  std::vector<ContestGuide> guide_list;

  ContestCoord first_coord = routing_segment.get_first();
  ContestCoord second_coord = routing_segment.get_second();

  int first_layer_idx = first_coord.get_layer_idx();
  int second_layer_idx = second_coord.get_layer_idx();
  if (first_layer_idx != second_layer_idx) {
    ContestUtil::swapByASC(first_layer_idx, second_layer_idx);
    for (int layer_idx = first_layer_idx; layer_idx <= second_layer_idx; layer_idx++) {
      ContestGuide guide;
      guide.set_lb_x(first_coord.get_x() * single_gcell_x_span);
      guide.set_lb_y(first_coord.get_y() * single_gcell_y_span);
      guide.set_rt_x((second_coord.get_x() + 1) * single_gcell_x_span);
      guide.set_rt_y((second_coord.get_y() + 1) * single_gcell_y_span);
      guide.set_layer_name(layer_idx_to_name_map[layer_idx]);
      guide_list.emplace_back(guide);
    }
  } else {
    ContestUtil::swapByCMP(first_coord, second_coord, CmpContestCoord());

    ContestGuide guide;
    guide.set_lb_x(first_coord.get_x() * single_gcell_x_span);
    guide.set_lb_y(first_coord.get_y() * single_gcell_y_span);
    guide.set_rt_x((second_coord.get_x() + 1) * single_gcell_x_span);
    guide.set_rt_y((second_coord.get_y() + 1) * single_gcell_y_span);
    guide.set_layer_name(layer_idx_to_name_map[first_layer_idx]);
    guide_list.emplace_back(guide);
  }
  return guide_list;
}

} // namespace ieda_contest